package org.kazuhiko.rpg.server;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Set;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.kazuhiko.rpg.server.gui.ServerFrame;
import org.kazuhiko.rpg.server.model.Player;
import org.kazuhiko.rpg.server.model.World;
import org.kazuhiko.rpg.server.net.RPGSession;
import org.kazuhiko.rpg.server.net.SessionHandler;
import org.kazuhiko.rpg.server.task.Task;
import org.kazuhiko.rpg.server.task.TaskQueue;
import org.kazuhiko.rpg.server.task.tasks.UpdateTask;
import org.kazuhiko.rpg.server.util.PlayerList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Server
{
	private static final Logger logger = LoggerFactory.getLogger( Server.class );
	
	private static Server instance;
	
	private final IoAcceptor acceptor = new NioSocketAcceptor();
	
	private final PlayerList players;
	
	private HashMap<String, SoftReference<World>> worlds;
	
	private int port;
	
	private boolean debugMode = true;
	
	public static Server getServer()
	{
		if( instance == null )
		{
			try
			{
				instance = new Server();
			}
			catch( IOException e )
			{
				logger.error( "Can't start server...", e );
			}
		}
		
		
		return instance;
	}
	
	public Server() throws IOException
	{
		logger.info( "Starting up server..." );
		
		init();
		
		players = new PlayerList();
		
		acceptor.setHandler( new SessionHandler() );
		
		worlds = new HashMap<String, SoftReference<World>>();
		
		//Create temporary world
		World world = new World();
		worlds.put( "default world", new SoftReference<World>( world ) );
	}
	
	private void init()
	{
		logger.info( "Configuring server..." );
		
		port = 49483;
	}
	
	public IoAcceptor getAcceptor()
	{
		return acceptor;
	}
	
	public int getPort()
	{
		return port;
	}
	
	public void start( boolean showGui ) throws IOException
	{
		logger.info( "ShowGUI: "+ showGui );
		ServerFrame sf = new ServerFrame();
		sf.showFrame();
		logger.info( "Initializing server..." );
		logger.info( "Starting tasks..." );
		TaskQueue.getTaskQueue().schedule( new UpdateTask() );
		logger.info( "Binding to port " + getPort() + "..." );
		acceptor.bind( new InetSocketAddress( getPort() ) );
		logger.info( "Ready for connections" );
	}
	
	public void taskError( Task task )
	{
		if( debugMode )
		{
			System.exit( 1 );
		}
	}
	
	@SuppressWarnings( "rawtypes" )
	public World[] getWorlds()
	{
		World[] ret = new World[ worlds.size() ];
		SoftReference[] refs = new SoftReference[ worlds.size() ];
		refs = worlds.values().toArray( refs );
		
		for( int i = 0; i < worlds.size(); i++ )
		{
			ret[i] = ( World ) refs[i].get();
		}
		
		return ret;
	}
	
	public Set<String> getWorldNames()
	{
		Set<String> ret = worlds.keySet();
		
		return ret;
	}
	
	public World getWorld( String name )
	{
		World world = worlds.get( name ).get();
		
		return world;
	}
	
	public int register( RPGSession session, String username )
	{
		final Player player = new Player( session, username );
		
		if( ! players.hasUsername( username ) )
		{
		
			if( !players.add( player ) )
			{
				logger.warn( "Too many players online!" );
				return 1;
			}
			
			session.setPlayer( player );
			
			assert( session.getPlayer().equals( player ) );
			player.moveToWorld( getWorld( "default world" ) );
			
			return 0;
		}
		
		return 2;
	}
	
	public int unregister( RPGSession session )
	{
		logger.trace( "Unregistering session." );
		
		if( session.getPlayer() != null )
		{
			World world = session.getPlayer().getWorld();
			session.getPlayer().setId( -1 );
			world.removePlayer( session.getPlayer() );
			players.remove( session.getPlayer() );
			
			return 0;
		}
		
		session.setPlayer( null );
		
		return 1;
	}
	
	public void stop()
	{
		logger.info( "Stopping server..." );
		acceptor.dispose();
		System.exit( 0 );
	}
}
